from S3D.AutoTagSearcher import Callback
from S3D.Macros import Macros
import traceback, sys
from S3D.Decorators import singleton

@singleton
class MacrosGenerator(Callback):
    def register(self, searcher):
        searcher.addGenerator("macros", self)
        searcher.addGenerator("python", self)

    def begin(self, info):
        self.map = {}

    def end(self, info):
        self.map = None

    def input(self, params, text, info):
        if len(params) == 0:
            info.addError("macros name and its parameters expected")
            return

        name = params[0]

        m           = Macros.fromString(text, info.command == "python")
        m.file      = info.file
        m.line      = info.line
        m.params    = params[1:]

        self.map[name] = m

    def output(self, params, info):
        return self.outputEx(params, info, {})

    def outputEx(self, params, info, locals):
        lengthGiven = len(params) - 1
        if lengthGiven < 0:
            info.addError("macros name and its parameters expected")
            return

        name = params[0]
        if name not in self.map.keys():
            info.addError("'%s': unknown macros name" % name)
            return

        m = self.map[name]

        params = params[1:]
        lengthExpected = len(m.params)
        if lengthGiven != lengthExpected:
            info.addError("'%s': wrong arguments count, %d arguments expected" % (name, len(m.params)))
            info.addErrorInfo("see declaration", m.file, m.line)
            return

        for k in range(lengthExpected):
            if k >= lengthGiven:
                locals[m.params[k]] = None
            else:
                locals[m.params[k]] = params[k]

        return self.run(m, locals, info)

    def run(self, macros, locals, info):
        try:
            return macros(locals)
        except:
            (exc_type, exc_info, exc_tb) = sys.exc_info()

            stack = traceback.extract_tb(exc_tb)
            lastFrame = stack[len(stack) - 1]

            if lastFrame[0] == "<string>":
                line = lastFrame[1]
                msg  = exc_info
            elif type(exc_info) is SyntaxError:
                line = exc_info.lineno
                msg  = exc_info.msg
            else:
                raise

            correction = 0
            for l in macros.lineCorrection:
                if line <= l:
                    correction += 1

            info.addError(msg, macros.file, macros.line + line + 1 - correction)
